import re
import os
from ruamel import yaml
from ruamel.yaml import YAML
from ruamel.yaml.emitter import Emitter
from autoconf_condition_parser import AutoconfConditionParser
import pprint

Emitter.MAX_SIMPLE_KEY_LENGTH = 4096

EXAMPLE_OUTPUT = """
depends:
  $name:
    type: lib|file|header|tool|prog
    feature: # set if only valid for feature
    required: true|false
    on_var: k=v
    set_var: [k=v]
features:
  $name:
    argv: --enable-xxx
    help: some help string
    default: true|false # if nil: no default
    set_var: [k=v]
"""

INVERT_VAL = {
    'true': 'false',
    'false': 'true',
    'yes': 'no',
    'no': 'yes',
    '1': '0',
    '0': '1',
}

VAL_TRUE = {
    'true': True,
    'yes': True,
    '1': True,
    'false': False,
    'no': False,
    '0': False,
}


class AutoconfDepends(AutoconfConditionParser):
    def __init__(self):
        super().__init__()
        self.infer_vars = {}
        self.ensure_vars = set()
        self.depends = {}
        self.features = {}

    def run_all(self):
        print("parse_macros")
        self.parse_macros()
        self.save_macros()

        print("parse_conditions")
        self.parse_conditions()
        self.save_conditions()

        print("build_depends")
        self.build_depends()
        self.save_depends()

        print("final-output")
        self.final_output()

    def final_output(self, dir=None):
        if dir is None:
            dir = self.configure_ac_dir
        # print(self.depends)
        final_depend = {}
        for key, value in self.depends.items():
            if 'on_var' in value:
                condition = 'buildRequires when ' + value['on_var']
            else:
                condition = 'buildRequires'
            this_value = key[:key.find('.') + 1] + value['value'].replace("] [", " ")
            final_depend.setdefault(condition, set()).add(this_value)
        for key, value in final_depend.items():
            final_depend[key] = list(value)
        with open(os.path.join(dir, "final_depends.yaml"), 'w', encoding='utf-8') as f:
            yml = yaml.YAML()
            yml.width = 10000
            yml.dump(final_depend, f)
        with open(os.path.join(dir, "final_options.yaml"), 'w', encoding='utf-8') as f:
            yml = yaml.YAML()
            yml.width = 10000
            yml.dump(self.features, f)
        return final_depend, self.features

    def build_depends(self, dir=None):
        if dir is None:
            dir = self.configure_ac_dir
        yml = YAML(typ='safe', pure=True)
        if not self.conditional_commands:
            self.conditional_commands = yml.load(open(os.path.join(dir, "conditions.yaml")))
        if not self.macros:
            self.macros = yml.load(open(os.path.join(dir, "macros.yaml")))
        self.map_vars()
        self.add_features()
        self.add_depends()
        self.add_depends_on_var()
        self.add_inter_depends()

    def save_depends(self, dir=None):
        if dir is None:
            dir = self.configure_ac_dir
        data = {
            'infer_vars': self.infer_vars,
            'ensure_vars': list(self.ensure_vars),
            'depends': self.depends,
            'features': self.features,
        }
        with open(os.path.join(dir, "depends.yaml"), 'w', encoding='utf-8') as f:
            yml = yaml.YAML()
            yml.dump(data, f)
        return data

    def split_help_string(self, s):
        match = re.match(r'\s*AS_HELP_STRING\(\s*\[(.*?)\]\s*,?\s*\[(.*)\]\s*\)\s*$', s)
        if match:
            return match.group(1), match.group(2)
        else:
            argv, _, help = s.partition(' ')
            return argv, help

    def add_features(self):
        for k, v in self.macros.items():
            match = None
            if re.match(r'^AC_ARG_(ENABLE)', k):
                match = re.match(r'^AC_ARG_(ENABLE)', k)
            if re.match(r'^AC_ARG_(WITH)', k):
                match = re.match(r'^AC_ARG_(WITH)', k)
            if match:
                kk = v.get('FEATURE')
                h = {
                    'name': kk,
                    'set_var': [f"{match.group(1).lower()}_{kk.replace('-', '_')}=yes"],
                }
                if 'HELP-STRING' in v:
                    h['argv'], h['help'] = self.split_help_string(v['HELP-STRING'])
                if 'ACTION-D' in v:
                    if re.match(r'([a-zA-Z_0-9]+)=["]?(true|yes|1)', v['ACTION-D']):
                        h['default'] = True
                        var, val = re.match(r'([a-zA-Z_0-9]+)=["]?(true|yes|1)', v['ACTION-D']).groups()
                        if f"{var}{val}" not in h['set_var']:
                            h['set_var'].append(f"{var}{val}")
                    elif re.match(r'([a-zA-Z_0-9]+)=["]?(false|no|0)', v['ACTION-D']):
                        h['default'] = False
                        var, val = re.match(r'([a-zA-Z_0-9]+=)?[\'"]?(false|no|0)', v['ACTION-D']).groups()
                        if f"{var}{INVERT_VAL[val]}" not in h['set_var']:
                            h['set_var'].append(f"{var}{INVERT_VAL[val]}")
                    elif re.match(r'([a-zA-Z_0-9]+=.*)', v['ACTION-D']):
                        h['set_var'].append(re.match(r'([a-zA-Z_0-9]+=.*)', v['ACTION-D']).group(1))
                    elif re.match(r'^--enable-', h['argv']):
                        h['default'] = False
                    elif re.match(r'^--disable-', h['argv']):
                        h['default'] = True

                self.features[kk] = h

    def get_macro_type(self, macro):
        i = macro.rstrip('_ONCE').rstrip('_FEATURE_CHECK').rfind('_')
        return macro[i + 1:].lower().rstrip('s')

    def add_depend_action(self, k, v):
        macro = k.split('.')[0]
        if macro == 'PKG_CHECK_MODULES' or macro == 'AC_SEARCH_LIBS':
            kk = self.AUTOCONF_MACRO_PARAMS[macro][1]
            if kk not in v:
                kk = self.AUTOCONF_MACRO_PARAMS[macro][0]
        else:
            kk = self.AUTOCONF_MACRO_PARAMS[macro][0]
        h = {
            'type': self.get_macro_type(macro),
            'value': v[kk] if isinstance(v[kk], str) else ' '.join(v[kk])
        }
        if 'ACTION-F' in v and re.match(r'([a-zA-Z_0-9]+=[\'"]?[a-zA-Z0-9-]+[\'"]?)', v['ACTION-F']):
            h['set_var'] = [re.match(r'([a-zA-Z_0-9]+=[\'"]?[a-zA-Z0-9-]+[\'"]?)', v['ACTION-F']).group(1).strip('\'"')]
        if 'ACTION-N' in v and re.match(r'AC_MSG_FAILURE|AC_MSG_ERROR', v['ACTION-N']):
            h['required'] = True
        self.depends[k] = h

    def get_first_word(self, v):
        a = re.split(r'[^a-zA-Z0-9_-]', v)
        return next(s for s in a if s)

    def add_depend_value(self, k, v):
        macro = k.split('.')[0]
        h = {
            'type': self.get_macro_type(macro),
            'value': v.get('PROG') or v.get('PROGS'),
        }

        if 'VALUE-F' in v:
            h['set_var'] = [f"{v['VARIABLE']}={v['VALUE-F']}"]
        elif 'PROG' in v:
            h['set_var'] = [f"{v['VARIABLE']}={self.get_first_word(v['PROG'])}"]
        elif 'PROGS' in v:
            h['set_var'] = [f"{v['VARIABLE']}={self.get_first_word(v['PROGS'])}"]

        self.depends[k] = h

    def add_depends(self):
        for k, v in self.macros.items():
            match = re.match(
                r'^AC_CHECK_HEADERS_ONCE|^AC_CHECK_FILE|^AC_CHECK_FILES|^AC_CHECK_HEADER|^AC_CHECK_HEADERS|^AC_CHECK_LIB|^AC_SEARCH_LIBS|^PKG_CHECK_MODULES|^PKG_CHECK_EXISTS',
                k)
            if match:
                self.add_depend_action(k, v)

            match = re.match(
                r'^AC_CHECK_PROGS|^AC_CHECK_PROG|^AC_CHECK_TARGET_TOOLS|^AC_CHECK_TARGET_TOOL|^AC_CHECK_TOOLS|^AC_CHECK_TOOL|^AC_PATH_PROGS_FEATURE_CHECK|^AC_PATH_PROGS|^AC_PATH_PROG|^AC_PATH_TARGET_TOOL|^AC_PATH_TOOL',
                k)
            if match:
                self.add_depend_value(k, v)

    def add_depends_on_var(self):
        for item in self.conditional_commands:
            cmd = item['command']
            cond = item['conditions']
            # print(cmd,cond)
            if len(cond) != 1:
                continue
            c0 = cond[0].strip('\'"')
            if re.search(r'\|\|', c0):
                ca = [c.strip() for c in c0.split('||') if not re.search(r'=(auto)?$', c.strip(), re.MULTILINE, )]
                if len(ca) != 1:
                    continue
                c0 = ca[0]
            match = re.match(r'(AC_CHECK_|AC_PATH_|AC_SEARCH_LIBS|PKG_CHECK_MODULES)(.*?)\(\s*\[?(.*?)\]?[,)]', cmd)
            if not match:
                continue
            k = f"{match.group(1)}{match.group(2)}.{match.group(3).strip()}"
            if k not in self.depends:
                continue
            match = re.search(r'([a-zA-Z_0-9]+) != (true|false|yes|no|0|1)', c0)
            if match:
                c0 = f"{match.group(1)} = {INVERT_VAL[match.group(2)]}"
            # print(c0)
            if tmp := self.reduce_test_kv(c0):
                self.depends[k]['on_var'] = tmp

    def add_inter_depends(self):
        for feat, ff in self.features.items():
            for feat_var in ff['set_var']:
                for dep, dd in self.depends.items():
                    if 'on_var' in dd:
                        if dd['on_var'] == feat_var:
                            dd['feature'] = feat
                    if 'set_var' in dd:
                        for dep_var in dd['set_var']:
                            if feat_var in self.infer_vars and dep_var == self.infer_vars[feat_var]:
                                dd['feature'] = feat
                                dd['required'] = True

    def reduce_var(self, v):
        return v.strip(' \'"${}')

    def reduce_val(self, v):
        return v.strip(' \'"')

    def reduce_test_kv(self, test):
        if test.startswith("! "):
            eq = False
            test = test[2:]
        else:
            eq = True
        if test.startswith("test"):
            test = test.lstrip('test ')
            if ' = ' in test:
                var, val = test.split(' = ')[:2]
                op = '=' if eq else '!='
            elif ' != ' in test:
                var, val = test.split(' != ')[:2]
                op = '!=' if eq else '='
            elif test.startswith('"x$'):
                var = test[2:]
                val = 'NULL'
                op = '!=' if eq else '='
            elif test.startswith('-f '):
                var = test[3:]
                val = 'EXIST'
                op = '=' if eq else '!='
            elif test.startswith('-n '):
                var = test[3:]
                val = 'NULL'
                op = '!=' if eq else '='
            else:
                # print(test)
                return ''
        elif test.startswith("expr"):
            test = test[5:]
            if ' : ' in test:
                var, val = test.split(' : ')[:2]
                if var.startswith('x'):
                    var = var[1:]
                    val = val[3:]
                    val = "'" + val[:val.find("'") + 1]
                op = '=' if eq else '!='
            else:
                print(test)
                return ''
        else:
            print(test)
            return ''
        return f"{self.reduce_var(var)} {op} {self.reduce_val(val or '')}"

    def map_var_pair(self, test1, test2):
        if not test2:
            return
        kv1 = self.reduce_test_kv(test1)
        kv2 = self.reduce_test_kv(test2)
        if '=' not in kv1 or '=' not in kv2:
            return
        if not re.search(r'[^-=.a-zA-Z_0-9]', kv1) and not re.search(r'[^-=.a-zA-Z_0-9]', kv2):
            if kv1.startswith(('enable_', 'with_')):
                self.infer_vars[kv1] = kv2
            else:
                self.infer_vars[kv2] = kv1

    def invert_test(self, test):
        if re.search(r' != ', test):
            return re.sub(r'!=', '=', test, count=1)
        elif re.search(r'= ""|= \'\'', test):
            return re.sub(r'= ""|= \'\'', '= NON_EMPTY', test)
        elif re.search(r'= [\'"]?(no|false|0)[\'"]?$', test):
            return re.sub(r'= [\'"]?(no|false|0)[\'"]?$', lambda m: f"= {INVERT_VAL[m.group(1)]}", test)
        else:
            # print(f"WARNING: failed to invert test '{test}'")
            return None

    def map_vars(self):
        for item in self.conditional_commands:
            cmd = item['command']
            cond = item['conditions']
            if re.search(r'^(AC_MSG_FAILURE|AC_MSG_ERROR)', cmd):
                if len(cond) == 1:
                    if '=' in cond[0]:
                        # self.ensure_vars.add(self.reduce_test_kv(cond[0]))
                        if itest := self.invert_test(cond[0]):
                            self.ensure_vars.add(self.reduce_test_kv(itest))
                elif len(cond) == 2:
                    if ' = ' in cond[0]:
                        self.map_var_pair(cond[0], self.invert_test(cond[1]))
                    elif ' = ' in cond[1]:
                        self.map_var_pair(cond[1], self.invert_test(cond[0]))

    def save_only_depends(self, dir, VAR_MAP, depends_file_name):
        self.parse_macros()
        self.save_macros()
        self.parse_conditions()
        self.save_conditions()
        self.build_depends()

        # 变量替换？需要吗？
        # extract_variables_and_update_var_map(self.lib_var_value, VAR_MAP)
        # self.libraries_dict = replace_variables(self.libraries_dict, VAR_MAP)

        # print(self.depends)
        final_depend = {}
        for key, value in self.depends.items():
            if 'on_var' in value:
                condition = 'buildRequires when ' + value['on_var']
            else:
                condition = 'buildRequires'
            this_value = key[:key.find('.') + 1] + value['value']
            final_depend.setdefault(condition, set()).add(this_value)
        for key, value in final_depend.items():
            final_depend[key] = list(value)

        with open(os.path.join(dir, depends_file_name), 'w', encoding='utf-8') as f:
            yml = yaml.YAML()
            yml.dump(final_depend, f)

        micros_file_path = os.path.join(self.configure_ac_dir, "macros.yaml")
        if os.path.exists(micros_file_path):
            # 删除多余临时文件
            os.remove(micros_file_path)
        conditions_file_path = os.path.join(self.configure_ac_dir, "conditions.yaml")
        if os.path.exists(conditions_file_path):
            # 删除多余临时文件
            os.remove(conditions_file_path)

    def parse_configure_options(self, file_path):
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
            configure_content = file.read()

        options_dict = {}
        match = re.search(r'Optional Features:(.*?)(?=Some influential environment variables:)', configure_content, re.DOTALL)
        sections = match.group(1).strip() if match else ''

        sections = re.sub(r'Optional Features:', '', sections)
        sections = re.sub(r'Optional Packages:', '', sections)

        if sections == '':
            return
        
        print(sections)
        lines = sections.splitlines()
        current_key = None
        current_help = []
        # special_flag = False
        
        for line in lines:
            line = line.strip()
            if line:
                # 检查是否是新的选项行
                match = re.match(r'(--(?:enable|disable|with|without)-[\w_-]+)(\[=\w+\])?(.*)', line)
                
                if match:
                    for i in range(1, match.lastindex + 1):
                        print(f"Group {i}: {match.group(i)}")
                    # 如果当前已有 key，先保存
                    if current_key:
                        options_dict[current_key]['help'] = ' '.join(current_help).strip()
                    
                    # 处理新的选项
                    argv = match.group(1).strip()
                    key = argv.replace('--enable-', '').replace('--disable-', '').replace('--with-', '').replace('--without-', '')
                    if key == 'PACKAGE' or key == 'FEATURE':
                        current_key = None
                        continue

                    clean_key = re.sub(r'[^a-zA-Z_-]', '', key)
                    print(f'clean_key:{clean_key}')
                    
                    current_key = clean_key
                    options_dict[current_key] = {'argv': argv}

                    # 处理 help 文本
                    current_help = [match.group(3).strip()]
                    print(f"current_help:{current_help}")
                    
                    # 处理 default 值
                    default_match = re.search(r'\[default=(yes|no)', line)

                    if default_match:
                        if default_match.group(1) == 'yes':
                            options_dict[current_key]['default'] = True
                        elif default_match.group(1) == 'no':
                            options_dict[current_key]['default'] = False
                    
                    options_dict[current_key]['name'] = current_key
                else:
                    current_help.append(line)
            else:
                # 处理空行：如果遇到空行且有当前键，则保存当前选项
                if current_key:
                    options_dict[current_key]['help'] = ' '.join(current_help).strip()
        # 添加最后一个选项
        if current_key:
            options_dict[current_key]['help'] = ' '.join(current_help).strip()
            options_dict[current_key]['name'] = current_key

        return options_dict

    def save_autotool(self, output_save_path):
        self.parse_macros()
        macros = self.save_macros(output_save_path)
        self.parse_conditions()
        conditions = self.save_conditions(output_save_path)
        self.build_depends(output_save_path)
        depends = self.save_depends(output_save_path)
        final_depends, final_options = self.final_output(output_save_path)
        # print(f"final_options:")
        # pprint.pprint(final_options)
        return macros, conditions, depends, final_depends, final_options


# parser = AutoconfDepends()
# parser.load_configure_ac(r'C:\Users\admin\Desktop\autotools-Test\NetworkManager-openconnect\NetworkManager-openconnect-1.2.8\configure.ac')
# parser.run_all()

# # parser.load_configure_ac(r'C:\Users\admin\Desktop\dependency-analysis\issue\issue02\bug_files\cryfs\vendor\googletest\gtest-1.8.0\googlemock\configure.ac')
# # parser.load_configure_ac(r'C:\Users\admin\Desktop\dependency-analysis\issue\issue02\bug_files\czmq\czmq-4.2.1\configure.ac')

# parser.run_all()
